package org.tacticalTroopers.jme.client;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.log4j.PropertyConfigurator;
import org.tacticalTroopers.jme.common.message.Command;
import org.tacticalTroopers.jme.common.message.Element;
import org.tacticalTroopers.jme.common.message.GameMap;
import org.tacticalTroopers.jme.common.message.GameState;
import org.tacticalTroopers.jme.common.message.Id;
import org.tacticalTroopers.jme.common.message.Information;
import org.tacticalTroopers.jme.common.message.PlayerNames;
import org.tacticalTroopers.jme.common.message.SerializationInitializer;
import org.tacticalTroopers.jme.common.message.Teams;
import org.tacticalTroopers.jme.common.message.utils.MessageListenerAdapter;

import com.jme3.app.Application;
import com.jme3.font.BitmapFont;
import com.jme3.font.BitmapText;
import com.jme3.font.Rectangle;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.PointLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.network.message.Message;
import com.jme3.renderer.queue.RenderQueue.Bucket;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial.CullHint;
import com.jme3.scene.shape.Box;
import com.jme3.system.AppSettings;
import com.jme3.util.TangentBinormalGenerator;

public class TacticalTroopersClient extends Application {

	private static final String EXIT = "SIMPLEAPP_Exit";
	private static final String RESET = "reset";
	private static final String FIRE = "fire";
	private static final String STRAF_LEFT = "strafLeft";
	private static final String STRAF_RIGHT = "strafRight";
	private static final String LEFT = "left";
	private static final String RIGHT = "right";
	private static final String BACKWARD = "backward";
	private static final String FORWARD = "forward";
	private static final String SWITCH_TEAM = "switchTeam";
	final static Log log = LogFactory.getLog(JmeClient.class);
	TacticalTroopersClient me = this;
	Node rootNode = new Node();
	Node  guiNode = new Node();

	Command command = new Command();
	Integer clientId;
	CameraNode camNode;
	com.jme3.network.connection.Client networkClient;
	PlayerNames playerNames;
	Teams teams;
	String playerName;

	@Override
	public void initialize(){
		super.initialize();
		Logger.getLogger("com.jme3").setLevel(Level.SEVERE);

		PropertyConfigurator.configure("log4j.properties");
		log.info("[CLIENT]");
		initConfiguration();
		log.info("PlayerName:"+playerName);
		viewPort.attachScene(rootNode);
		inputManager.addMapping(EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE));
		inputManager.addMapping(RESET, new KeyTrigger(KeyInput.KEY_RETURN));
		inputManager.addMapping(FORWARD, new KeyTrigger(KeyInput.KEY_I));
		inputManager.addMapping(BACKWARD, new KeyTrigger(KeyInput.KEY_K));
		inputManager.addMapping(LEFT, new KeyTrigger(KeyInput.KEY_J));
		inputManager.addMapping(RIGHT, new KeyTrigger(KeyInput.KEY_L));
		inputManager.addMapping(STRAF_LEFT, new KeyTrigger(KeyInput.KEY_A));
		inputManager.addMapping(STRAF_RIGHT, new KeyTrigger(KeyInput.KEY_Z));
		inputManager.addMapping(FIRE, new KeyTrigger(KeyInput.KEY_SPACE));
		inputManager.addMapping(SWITCH_TEAM, new KeyTrigger(KeyInput.KEY_F1));

		inputManager.addListener(new AnalogListener() {

			@Override
			public void onAnalog(String name, float value, float tpf) {
				if(name.equals(FORWARD)){
					command.setAccel(10f);
				}else if(name.equals(BACKWARD)){
					command.setAccel(-10f);
				}else if(name.equals(RIGHT)){
					command.setTurn(-1f);
				}else if(name.equals(LEFT)){
					command.setTurn(1f);
				}else if(name.equals(STRAF_LEFT)){
					command.setStraf(6f);
				}else if(name.equals(STRAF_RIGHT)){
					command.setStraf(-6f);
				}else if(name.equals(FIRE)){
					command.setFire(true);
				}else if(name.equals(RESET)){
					command.setOrder(Command.RESET);
				}else if(name.equals(SWITCH_TEAM)){
					command.setOrder(Command.SWITCH_TEAM);
				}
			}
		}, FORWARD,BACKWARD,RIGHT,LEFT,STRAF_RIGHT,STRAF_LEFT,FIRE,RESET,SWITCH_TEAM);

		inputManager.addListener(new ActionListener() {

			@Override
			public void onAction(String name, boolean keyPressed, float tpf) {
				if (!keyPressed)
					return;
				if (name.equals(EXIT)){
					command.setOrder(Command.QUIT);
					try {
						networkClient.disconnect();
					} catch (IOException e) {
						log.error(e);
					}
					stop();
				}
			}
		},EXIT);


		SerializationInitializer.init();
		networkClient = new com.jme3.network.connection.Client();
		final com.jme3.network.connection.Client c = networkClient;
		try {
			c.connect("localhost",56650,56651);
		} catch (IllegalArgumentException e) {
			log.error(e);
		} catch (IOException e) {
			log.error(e);
		}

		c.addMessageListener(new MessageListenerAdapter() {
			@Override
			public void messageReceived(Message message) {
				String info = ((Information)message).getValue();
				log.info("Received Information:"+info);
				scoreText.setText(info);
			}
		},Information.class);

		c.addMessageListener(new MessageListenerAdapter() {
			@Override
			public void messageReceived(Message message) {
				clientId = ((Id)message).getValue();
				log.info("Received Id:"+clientId);
				try {
					log.info("Send Player Name");
					c.send(new Information().setKey("playerName").setValue(playerName));
				} catch (IOException e) {
					log.error(e);
				}
			}
		},Id.class);

		c.addMessageListener(new MessageListenerAdapter() {
			@Override
			public void messageReceived(Message message) {
				final GameMap gm = (GameMap)message;
				log.info("Received GameMap:"+gm);
				me.enqueue(new Callable<Void>() {
					public Void call() throws Exception {
						displayGameMap(gm,rootNode);		
						return null;
					}
				});


				displayGameMap((GameMap)message, rootNode);
			}
		},GameMap.class);

		c.addMessageListener(new MessageListenerAdapter() {
			@Override
			public void messageReceived(Message message) {
				final GameState gs=(GameState)message;
				log.debug("Received GameState:"+gs);
				me.enqueue(new Callable<Void>() {
					public Void call() throws Exception {
						displayGameState(gs,rootNode);		
						return null;
					}
				});
				try {
					log.debug("Send Command "+command);
					c.send(command);
					initCommand();
				} catch (IOException e) {
					log.error(e);
				}
			}
		},GameState.class);


		c.addMessageListener(new MessageListenerAdapter() {
			@Override
			public void messageReceived(Message message) {
				final PlayerNames gm = (PlayerNames)message;
				log.info("Received PlayerNames:"+gm);
				playerNames = gm;
			}
		},PlayerNames.class);

		c.addMessageListener(new MessageListenerAdapter() {
			@Override
			public void messageReceived(Message message) {
				final Teams gm = (Teams)message;
				log.info("Received Teams:"+gm);
				teams = gm;
			}
		},Teams.class);

		c.start();




		guiNode = new Node();
		guiNode.setQueueBucket(Bucket.Gui);
		guiNode.setCullHint(CullHint.Never);



		guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
		fpsText = new BitmapText(guiFont, false);
		fpsText.setSize(guiFont.getCharSet().getRenderedSize());
		fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0);
		fpsText.setText("Frames per second");
		fpsText.setColor(ColorRGBA.White);
		guiNode.attachChild(fpsText);

		scoreText = new BitmapText(guiFont, false);
		scoreText.setSize(guiFont.getCharSet().getRenderedSize());
		scoreText.setLocalTranslation(0, scoreText.getLineHeight()+20*fpsText.getLineHeight(), 0);
		scoreText.setText("Score :");
		scoreText.setColor(ColorRGBA.White);
		guiNode.attachChild(scoreText);

		guiViewPort.attachScene(guiNode);

		
		Float x = 25f;
		Float y = 20f;
		Float z = 10f;
		Float range= 70f;
		
		PointLight lamp_light = new PointLight();
		lamp_light.setColor(ColorRGBA.White);
		lamp_light.setRadius(range);
		lamp_light.setPosition(new Vector3f(-x,y,-z));
		rootNode.addLight(lamp_light);

		PointLight lamp_light2 = new PointLight();
		lamp_light2.setColor(ColorRGBA.White);
		lamp_light2.setRadius(range);
		lamp_light2.setPosition(new Vector3f(x,y,-z));
		rootNode.addLight(lamp_light2);
		
		PointLight lamp_light3 = new PointLight();
		lamp_light3.setColor(ColorRGBA.White);
		lamp_light3.setRadius(range);
		lamp_light3.setPosition(new Vector3f(-x,y,z));
		rootNode.addLight(lamp_light3);
		
		PointLight lamp_light4 = new PointLight();
		lamp_light4.setColor(ColorRGBA.White);
		lamp_light4.setRadius(range);
		lamp_light4.setPosition(new Vector3f(x,y,z));
		rootNode.addLight(lamp_light4);
		
		blueMat = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
		blueMat.setColor("m_Color", ColorRGBA.Blue);
		redMat = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
		redMat.setColor("m_Color", ColorRGBA.Red);
		whiteMat = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
		whiteMat.setColor("m_Color", ColorRGBA.Gray);
		defaultMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
		defaultMat.setFloat("m_Shininess", 12);
		defaultMat.setBoolean("m_UseMaterialColors", true);
		defaultMat.setColor("m_Ambient",  ColorRGBA.Black);
		defaultMat.setColor("m_Diffuse",  ColorRGBA.Gray);
		defaultMat.setColor("m_Specular", ColorRGBA.Gray);

	}
	Material defaultMat;
	Material blueMat;
	Material redMat;
	Material whiteMat;
	BitmapText fpsText,scoreText;	
	BitmapFont guiFont;

	void initCommand() {
		command.setAccel(0f);
		command.setTurn(0f);
		command.setFire(false);
		command.setStraf(0f);
		command.setOrder(null);
	}

	protected float secondCounter = 0.0f;

	@Override
	public void update() {
		//if (speed == 0 || paused)
		//  return;

		super.update();
		float tpf = timer.getTimePerFrame() * speed;


		int fps = (int) timer.getFrameRate();
		secondCounter += timer.getTimePerFrame();
		if (secondCounter >= 1.0f){
			if(fpsText!=null){
				fpsText.setText("Frames per second: "+fps);
			}
			secondCounter = 0.0f;
		}

		// update states
		stateManager.update(tpf);

		// simple update and root node
		rootNode.updateLogicalState(tpf);
		guiNode.updateLogicalState(tpf);
		rootNode.updateGeometricState();
		guiNode.updateGeometricState();

		// render states
		stateManager.render(renderManager);
		renderManager.render(tpf);
	}


	Vector3f up = new Vector3f(0f,1f,0f);
	Map<Integer,Node> nodes = new HashMap<Integer,Node>();
	List<Node> nodesToRemove = new ArrayList<Node>();

	
	
	void displayGameState(GameState gs,Node rootNode){
		nodesToRemove.clear();
		nodesToRemove.addAll(nodes.values());

		for(Integer currentElementId : gs.getDynamicElements().keySet()){
			Element e = gs.getDynamicElements().get(currentElementId);

			Node currentNode = nodes.get(currentElementId);
			if(currentNode==null){
				Vector3f size = e.getSize();
				Node newNode= new Node();
				newNode.setLocalTranslation(e.getPosition());
				newNode.setLocalRotation(e.getRotation());
				rootNode.attachChild(newNode);
				nodes.put(e.getId().getValue(),newNode);

				Geometry body = new Geometry("Box", new Box( new Vector3f(), size.x,size.y,size.z));
				TangentBinormalGenerator.generate(body.getMesh(), true);
				
				body.setMaterial(defaultMat);
				newNode.attachChild(body);

				if(e.getType().equals(Element.PLAYER)){
					Geometry head = new Geometry("head", new Box( new Vector3f(0f,0.50f,0.5f), size.x/2,size.y/2,size.z/2));
					head.setMaterial(whiteMat);		
					newNode.attachChild(head);
					
					Geometry gun = new Geometry("gun", new Box( new Vector3f(-1f,.5f,0f), .1f,.1f,.7f));
					gun.setMaterial(whiteMat);
					newNode.attachChild(gun);
							

					if(currentElementId.equals(clientId)){					
						camNode = new CameraNode(cam);
						camNode.setLocalTranslation(-1f,.6f,-5f);
						newNode.attachChild(camNode);
					}
					
					


					BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt");
					BitmapText txt = new BitmapText(fnt, false);
					txt.setBox(new Rectangle(0, 0, 6, 3));
					txt.setLocalTranslation(1f, 0.0f, -0.7f);
					txt.rotate(0, FastMath.PI, 0);
					txt.setQueueBucket(Bucket.Transparent);
					txt.setSize( 0.4f );
					txt.setName("playerName");
					newNode.attachChild(txt);
					BitmapText txt2 = new BitmapText(fnt, false);
					txt2.setBox(new Rectangle(0, 0, 6, 3));
					txt2.setLocalTranslation(-1f, 0.0f, 0.7f);
					txt2.setQueueBucket(Bucket.Transparent);
					txt2.setSize( 0.4f );
					txt2.setName("playerName2");
					newNode.attachChild(txt2);


				}else if(e.getType().equals(Element.BULLET)){
					Geometry head = new Geometry("Box", new Box( new Vector3f(0f,0f,0.0f), size.x/2,size.y/2,size.z*2));
					Material mat2 = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
					mat2.setColor("m_Color", ColorRGBA.Red);
					head.setMaterial(mat2);

					newNode.attachChild(head);
				}
				currentNode=newNode;
			}else{
				nodesToRemove.remove(currentNode);
				currentNode.setLocalTranslation(e.getPosition());
				currentNode.setLocalRotation(e.getRotation());
			}

			if(playerNames!=null){
				
				String playerName=playerNames.getPlayerName(currentElementId); 
				if(playerName!=null){
					BitmapText bt = (BitmapText)currentNode.getChild("playerName");
					bt.setText(playerName);
					bt = (BitmapText)currentNode.getChild("playerName2");
					bt.setText(playerName);
				}
			}
			
			if(teams!=null){
				String playerTeam=teams.getPlayerTeam(currentElementId); 
				if(playerTeam!=null){
					Geometry bt = (Geometry)currentNode.getChild("head");
					if(playerTeam.equals("Blue")){
						bt.setMaterial(blueMat);
					}else if(playerTeam.equals("Red")){
						bt.setMaterial(redMat);
					}
				}
			}

		}

		for(Node node:nodesToRemove){
			rootNode.detachChild(node);
		}
	}

	Quaternion tmpQuat= new Quaternion();

	void displayGameMap(GameMap gm,Node rootNode){

		for(Element wall : gm.getWalls()){
			if(!wall.getAttributeBoolean("hidden", false)){
				
			Vector3f size = wall.getSize();
			Geometry box = new Geometry("Box", new Box( new Vector3f(), size.x,size.y,size.z));
			//Geometry box = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj");
			TangentBinormalGenerator.generate(box.getMesh(), true);
			Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md");
			mat.setFloat("m_Shininess", 12);
			mat.setBoolean("m_UseMaterialColors", true);
			mat.setColor("m_Ambient",  ColorRGBA.Black);
			mat.setColor("m_Diffuse",  ColorRGBA.Gray);
			mat.setColor("m_Specular", ColorRGBA.Gray);
			/*
			Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/SolidColor.j3md");
			if(wall.getId().getValue()==1){
				mat1.setColor("m_Color", ColorRGBA.Gray);
			}else{
				mat1.setColor("m_Color", ColorRGBA.Blue);
			}
			 */
			box.setMaterial(mat);

			Node n= new Node();
			n.attachChild(box);
			n.setLocalTranslation(wall.getPosition());
			n.setLocalRotation(wall.getRotation());
			rootNode.attachChild(n);
			nodes.put(wall.getId().getValue(),n);

			rootNode.attachChild(n);
			}
		}
	}

	public void initConfiguration() {

		File conf = new File("tacticalTrooper.cfg");
		if(!conf.exists()){
			FileWriter fw;
			try {
				fw = new FileWriter(conf);
				fw.append("playerName=NameLessTrooper");
				fw.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}

		Scanner scanner=null;
		try {
			scanner = new Scanner(new FileInputStream(conf));
			while (scanner.hasNextLine()){
				playerName=scanner.nextLine().substring(11);
			}
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}finally{
			scanner.close();
		}

	}

	public static void main(String[] args) {
		TacticalTroopersClient c = new TacticalTroopersClient();
		AppSettings appSetting = new AppSettings(true);
		appSetting.setTitle("Client");
		appSetting.setVSync(true);

		//appSetting.setRenderer(AppSettings.JOGL);
		c.setSettings(appSetting);
		c.start();
	}


}

